﻿using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
using Unis.Framework.Core.Utility;

namespace Unis.Framework.ORM.Emit
{
    public class DynamicPropertyAccessor:IDynamicPropertyAccessor
    {
        private GetPropertyDelegate _GetDelegate;
        private PropertyInfo _PropertyInfo;
        private Type _PropertyType;
        private SetPropertyDelegate _SetDelegate;
        private Type _TargetType;
        private static Hashtable _TYPE_HASH;

        static DynamicPropertyAccessor()
        {
            InitTypes();
        }

        public DynamicPropertyAccessor(Type targetType, PropertyInfo propertyInfo)
            : this(targetType, null, propertyInfo)
        {
        }

        public DynamicPropertyAccessor(Type targetType, string property)
            : this(targetType, property, targetType.GetProperty(property))
        {
        }

        private DynamicPropertyAccessor(Type targetType, string propertyName, PropertyInfo propertyInfo)
        {
            this._TargetType = targetType;
            this._PropertyInfo = propertyInfo;
            if ((propertyName == null) && (propertyInfo != null))
            {
                propertyName = propertyInfo.Name;
            }
            if (this._PropertyInfo == null)
            {
                throw new DynamicPropertyAccessorException(string.Format("属性 \"{0}\" 在当前类型 {1} 中不存在", propertyName, targetType));
            }
            this._PropertyType = propertyInfo.PropertyType;
        }

        public object Get(object target)
        {
            if (!this._PropertyInfo.CanRead)
            {
                throw new DynamicPropertyAccessorException(string.Format("属性 \"{0}\" 不存在一个 GET 方法.", this._PropertyInfo.Name));
            }
            if (this._GetDelegate == null)
            {
                this._GetDelegate = this.getPropertyorValue(this._PropertyInfo);
            }
            return this._GetDelegate(target);
        }

        private GetPropertyDelegate getPropertyorValue(PropertyInfo pi)
        {
            string name = pi.Name;
            Module m = pi.Module;
            DynamicMethod method = new DynamicMethod("GET_" + name, typeof(object), new Type[] { typeof(object) }, m, true);
            ILGenerator iLGenerator = method.GetILGenerator();
            iLGenerator.Emit(OpCodes.Ldarg_0);
            if (pi != null)
            {
                iLGenerator.EmitCall(OpCodes.Callvirt, pi.GetGetMethod(), null);
                if (pi.PropertyType.IsValueType)
                {
                    iLGenerator.Emit(OpCodes.Box, pi.PropertyType);
                }
            }
            iLGenerator.Emit(OpCodes.Ret);
            return (GetPropertyDelegate)method.CreateDelegate(typeof(GetPropertyDelegate));
        }

        private static void InitTypes()
        {
            _TYPE_HASH = new Hashtable();
            _TYPE_HASH[typeof(sbyte)] = OpCodes.Ldind_I1;
            _TYPE_HASH[typeof(byte)] = OpCodes.Ldind_U1;
            _TYPE_HASH[typeof(char)] = OpCodes.Ldind_U2;
            _TYPE_HASH[typeof(short)] = OpCodes.Ldind_I2;
            _TYPE_HASH[typeof(ushort)] = OpCodes.Ldind_U2;
            _TYPE_HASH[typeof(int)] = OpCodes.Ldind_I4;
            _TYPE_HASH[typeof(uint)] = OpCodes.Ldind_U4;
            _TYPE_HASH[typeof(long)] = OpCodes.Ldind_I8;
            _TYPE_HASH[typeof(ulong)] = OpCodes.Ldind_I8;
            _TYPE_HASH[typeof(bool)] = OpCodes.Ldind_I1;
            _TYPE_HASH[typeof(double)] = OpCodes.Ldind_R8;
            _TYPE_HASH[typeof(float)] = OpCodes.Ldind_R4;
        }

        public void Set(object target, object value)
        {
            if (!this._PropertyInfo.CanWrite)
            {
                throw new DynamicPropertyAccessorException(string.Format("属性 \"{0}\" 不存在一个SET 方法.", this._PropertyInfo.Name));
            }
            if (this._SetDelegate == null)
            {
                this._SetDelegate = this.setPropertyValue(this._TargetType, this._PropertyInfo);
            }
            object obj2 = Reflection.ConvertValueType(this._PropertyInfo.PropertyType, value);
            this._SetDelegate(target, obj2);
        }

        private SetPropertyDelegate setPropertyValue(Type oType, PropertyInfo pi)
        {
            string name = pi.Name;
            DynamicMethod method = new DynamicMethod("SET_" + name, typeof(void), new Type[] { typeof(object), typeof(object) }, pi.Module, true);
            ILGenerator iLGenerator = method.GetILGenerator();
            iLGenerator.Emit(OpCodes.Ldarg_0);
            iLGenerator.Emit(OpCodes.Ldarg_1);
            Type propertyType = pi.PropertyType;
            if (propertyType.IsValueType)
            {
                iLGenerator.Emit(OpCodes.Unbox, propertyType);
                if (_TYPE_HASH[propertyType] != null)
                {
                    OpCode opcode = (OpCode)_TYPE_HASH[propertyType];
                    iLGenerator.Emit(opcode);
                }
                else
                {
                    iLGenerator.Emit(OpCodes.Ldobj, propertyType);
                }
            }
            else
            {
                iLGenerator.Emit(OpCodes.Castclass, propertyType);
            }
            iLGenerator.EmitCall(OpCodes.Callvirt, pi.GetSetMethod(true), null);
            iLGenerator.Emit(OpCodes.Ret);
            return (SetPropertyDelegate)method.CreateDelegate(typeof(SetPropertyDelegate));
        }

        public Type PropertyType
        {
            get
            {
                return this._PropertyType;
            }
        }

        private delegate object GetPropertyDelegate(object obj);

        private delegate void SetPropertyDelegate(object obj, object m_Value);
    }
}
